package jmine.tec.persist.naturalkey;

import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import jmine.tec.utils.collection.impl.ClassHierarchyMap;
import jmine.tec.utils.register.Receiver;
import bancosys.tec.persist.Persistable;
import bancosys.tec.persist.exception.NaturalKeyNotDefinedException;

/**
 * Registro de natural key. Cria objetos que contém as informações relativas à natural key das entidades.
 * 
 * @author lundberg
 */
public class NaturalKeyHandlerFactory implements Receiver<NaturalKeyHandler> {

    private Map<Class<?>, NaturalKeyConverter<?>> converters;

    private Map<Class<?>, NaturalKeyHandler> handlerMap;

    private Map<Class<?>, NaturalKeyHandler> customHandlerLookup;

    private Map<Class<? extends Persistable>, Class<? extends Persistable>> entitiesWithoutNaturalKey;

    /**
     * Construtor
     */
    public NaturalKeyHandlerFactory() {
        this.converters = new ConcurrentHashMap<Class<?>, NaturalKeyConverter<?>>();
        this.handlerMap = new ConcurrentHashMap<Class<?>, NaturalKeyHandler>();
        this.customHandlerLookup = new ClassHierarchyMap<NaturalKeyHandler>();
        this.entitiesWithoutNaturalKey = new ConcurrentHashMap<Class<? extends Persistable>, Class<? extends Persistable>>();
        // É possível registrar pelo spring mais converters. Os converters básicos são carregados no próprio construtor
        // pois alguns beans criados pelo spring criam os DAOs no próprio construtor, o que pode acontecer antes do registro
        // dos converters.
        this.registerConverters(Arrays
                .asList(new NaturalKeyConverter<?>[]{ new StringNaturalKeyConverter(), new LongNaturalKeyConverter() }));
    }

    /**
     * Obtém as propriedades de natural key da entidade.
     * 
     * @param clazz classe cujas propriedades de natural key devem ser obtidas
     * @return NaturalKeyProperties (null caso a classe não possua natural key anotada).
     */
    public NaturalKeyHandler findNaturalKeyProperties(Class<? extends Persistable> clazz) {
        if (this.entitiesWithoutNaturalKey.containsKey(clazz)) {
            return null;
        }
        if (!this.handlerMap.containsKey(clazz)) {
            if (this.customHandlerLookup.containsKey(clazz)) {
                this.handlerMap.put(clazz, this.customHandlerLookup.get(clazz));
            } else {
                try {
                    this.handlerMap.put(clazz, new DefaultNaturalKeyHandler(clazz, this));
                } catch (NaturalKeyNotDefinedException e) {
                    this.entitiesWithoutNaturalKey.put(clazz, clazz);
                    return null;
                }
            }
        }
        return this.handlerMap.get(clazz);
    }

    /**
     * Registra um conversor de natural key
     * 
     * @param registers conversores que devem ser registrados
     */
    public void registerConverters(Collection<? extends NaturalKeyConverter<?>> registers) {
        for (NaturalKeyConverter<?> converter : registers) {
            this.converters.put(converter.getTargetClass(), converter);
        }
    }

    /**
     * Retorna o conversos para o tipo dado
     * 
     * @param type type
     * @return NaturalKeyConverter
     */
    NaturalKeyConverter<?> getConverter(Class<?> type) {
        return this.converters.get(type);
    }

    /**
     * {@inheritDoc}
     */
    public void register(Collection<? extends NaturalKeyHandler> registers) {
        for (NaturalKeyHandler naturalKeyHandler : registers) {
            this.customHandlerLookup.put(naturalKeyHandler.getTargetClass(), naturalKeyHandler);
        }
    }
}
